﻿using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.XPath;

namespace WordBinder.Binder
{
    public class ReflectBinder
    {
        private Config _config;
        private List<string> _missingTokens;
        private List<string> _missingDataTokens;
        private XmlNamespaceManager TemplateNS, DocNS;

        public List<string> MissingDataTokens
        {
            get { return _missingDataTokens; }
        }

        public List<string> MissingTokens
        {
            get { return _missingTokens; }
        }

        public ReflectBinder(Config config)
        {
            _config = config;
        }

        public ReflectBinder()
            : this(new Config { AllowPartialBinding = true, DataSearch = DataSearchType.Reflection })
        {
        }

        public void BuildDocument(string wordFilePath, object data, Map map)
        {
            string FileTarget = string.Format("{0}\\{1}_output{2}", Path.GetDirectoryName(wordFilePath),
                                                           Path.GetFileNameWithoutExtension(wordFilePath),
                                                           Path.GetExtension(wordFilePath));
            File.Copy(wordFilePath, FileTarget, true);
            Stream wordFile = File.Open(FileTarget, FileMode.Open, FileAccess.ReadWrite);

            BuildDocument(wordFile, data, map);

            wordFile.Dispose();
        }

        private void DeleteTemplateDocument(string filename)
        {
            if (File.Exists(filename))
                File.Delete(filename);
        }

        private bool BuildDocument(Stream wordFile, object data, Map map)
        {
            XmlDocument BindableDoc;
            _missingTokens = new List<string>();
            _missingDataTokens = new List<string>();

            using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(wordFile, true))
            {
                BindableDoc = new XmlDocument();
                BindableDoc.Load(wdDoc.MainDocumentPart.GetStream());
                TemplateNS = GetNMFromXML(BindableDoc);

                if (map.Header != null)
                {
                    var headers = wdDoc.MainDocumentPart.HeaderParts.ToList();
                    foreach (var h in headers)
                    {
                        var hbinder = new XmlDocument();
                        hbinder.Load(h.GetStream());
                        BuildComplexItem(wdDoc.MainDocumentPart, hbinder, data, map.Header);
                        hbinder.Save(h.GetStream(FileMode.Create, FileAccess.Write));
                    }
                }

                if (map.Footer != null)
                {
                    var footers = wdDoc.MainDocumentPart.FooterParts.ToList();
                    foreach (var f in footers)
                    {
                        var fbinder = new XmlDocument();
                        fbinder.Load(f.GetStream());
                        BuildComplexItem(wdDoc.MainDocumentPart, fbinder, data, map.Header);
                        fbinder.Save(f.GetStream(FileMode.Create, FileAccess.Write));
                    }
                }

                if (map.Body != null)
                {
                    var body = new XmlDocument();
                    body.Load(wdDoc.MainDocumentPart.GetStream());
                    BuildComplexItem(wdDoc.MainDocumentPart, body, data, map.Body);
                    body.Save(wdDoc.MainDocumentPart.GetStream(FileMode.Create, FileAccess.Write));
                }
            }

            return true;
        }

        private bool BuildComplexItem(MainDocumentPart wdPart, XmlNode currentNode, object data, ComplexItem complexItem)
        {
            XmlNode TemplateNode = null, NewRowRepeatingNode = null, ParentTableRepeatingNode = null;
            List<object> renderingData;
            object dataresult = null;

            if (complexItem.SimpleItems == null || complexItem.SimpleItems.Count == 0)
                return true;

            if (complexItem.Type == ComplexType.Items && complexItem.SimpleItems != null)
            {
                var simpleitems = complexItem.SimpleItems;

                foreach (var si in simpleitems)
                {
                    TemplateNode = null;

                    TemplateNode = currentNode.SelectSingleNode(string.Format("//w:t[text() = '{0}']", si.Key), TemplateNS);
                    dataresult = data.SelectSingle(si.XPath);

                    if (TemplateNode == null)
                    {
                        if (!_config.AllowPartialBinding)
                            throw new Exception(string.Format("Missing token {0}", si.Key));

                        _missingTokens.Add(si.Key);
                        continue;
                    }
                    if (dataresult == null)
                    {
                        _missingDataTokens.Add(si.Key);
                        continue;
                    }

                    SetValue(TemplateNode, dataresult, si);
                }

                #region RepeatingSection
                if (complexItem.ComplexItems != null)
                {
                    foreach (var ci in complexItem.ComplexItems)
                    {
                        BuildComplexItem(wdPart, currentNode, data, ci);
                    }
                }
                #endregion
            }
            else if (complexItem.Type == ComplexType.Rows || complexItem.Type == ComplexType.Table)
            {
                var locatorToken = complexItem.SimpleItems[0];

                var querydata = data.Query(complexItem.RootNodePath);

                if (!querydata.Any())
                    return false;

                renderingData = querydata.ToList();

                var templatehint = currentNode.SelectSingleNode(string.Format("//w:t[text() = '{0}']", locatorToken.Key), TemplateNS);
                if (templatehint == null)
                    return false;

                if (complexItem.Type == ComplexType.Rows)
                {
                    ParentTableRepeatingNode = templatehint.SelectSingleNode("ancestor::w:tbl[1]", TemplateNS);
                    TemplateNode = templatehint.SelectSingleNode("ancestor::w:tr[1]", TemplateNS);
                }
                else
                {
                    ParentTableRepeatingNode = templatehint.SelectSingleNode("ancestor::w:tbl[2]", TemplateNS);
                    TemplateNode = templatehint.SelectSingleNode("ancestor::w:tr[2]", TemplateNS);
                }

                NewRowRepeatingNode = TemplateNode.CloneNode(true);

                if (TemplateNode == null || ParentTableRepeatingNode == null)
                {
                    //_missingTokens.Add(Key);
                    return false;
                }

                TemplateNode.ParentNode.RemoveChild(TemplateNode);

                foreach (var item in renderingData)
                {
                    var currentTemplate = NewRowRepeatingNode.CloneNode(true);

                    InsertSimpleItemValues(currentTemplate, item, complexItem.SimpleItems);

                    if (complexItem.ComplexItems != null)
                    {
                        foreach (var childci in complexItem.ComplexItems)
                            BuildComplexItem(wdPart, currentTemplate, item, childci);
                    }
                    ParentTableRepeatingNode.AppendChild(currentTemplate);
                }
            }

            #region Images

            XmlNode ParentNode = null;
            int ImageNumber = 0;
            Bitmap imgdata = null;
            Stream imgStream = null;
            string ImageName;
            if (complexItem.ImageItems != null)
            {
                foreach (var img in complexItem.ImageItems)
                {
                    ImageName = string.Format("image{0}.png", ImageNumber++);

                    TemplateNode = currentNode.SelectSingleNode(string.Format("//w:t[text() = '{0}']", img.Key), TemplateNS);
                    ParentNode = TemplateNode.SelectSingleNode("ancestor::w:r[1]", TemplateNS);
                    ParentNode.RemoveAll();

                    var rawdata = data.SelectSingle(img.XPath);
                    if (img.Type == ImageType.Image)
                    {
                        imgdata = (Bitmap)rawdata;
                        imgStream = new MemoryStream();
                        imgdata.Save(imgStream, ImageFormat.Png);
                    }
                    if (img.Type == ImageType.ByteArray)
                    {
                        imgStream = new MemoryStream((byte[])rawdata);
                        imgdata = (Bitmap)Bitmap.FromStream(imgStream);
                    }
                    if (img.Type == ImageType.Base64)
                    {
                        var RawImageData = Convert.FromBase64String((string)rawdata);
                        imgStream = new MemoryStream(RawImageData);
                        imgdata = (Bitmap)Bitmap.FromStream(imgStream);
                    }

                    if (ParentNode == null)
                    {
                        _missingTokens.Add(img.Key);
                        continue;
                    }
                    if (imgdata == null)
                    {
                        TemplateNode.InnerXml = "{DataNotFound}";
                        continue;
                    }

                    var imagePart = wdPart.AddImagePart(ImagePartType.Gif);

                    imgStream.Position = 0;
                    imagePart.FeedData(imgStream);

                    InserImage(ParentNode, wdPart.GetIdOfPart(imagePart), imgdata, ImageName);
                }

            }
            #endregion

            return true;
        }

        private void SetValue(XmlNode TokenNode, object dataNode, SimpleItem si)
        {

            if (si.Masks != null && si.Masks.Count != 0)
            {
                var replace = si.Masks.FirstOrDefault(m => string.Compare(m.Value.ToString(), dataNode.ToString(), true) == 0);
                if (replace != null)
                {
                    if (!string.IsNullOrEmpty(replace.Format))
                        TokenNode.InnerXml = FormatValue(replace.Replace, replace.Format);
                    else if (!string.IsNullOrEmpty(si.Format))
                        TokenNode.InnerXml = FormatValue(replace.Replace, si.Format);
                    else
                        TokenNode.InnerXml = replace.Replace.ToString();
                }
            }
            else
            {
                TokenNode.InnerXml = FormatValue(dataNode, si.Format);
            }
        }

        private string FormatValue(object value, string format)
        {
            if (!string.IsNullOrEmpty(format))
                return string.Format(format, value);
            else
                return string.Format("{0}", value);
        }

        private bool InsertSimpleItemValues(XmlNode TemplateNode, object data, List<SimpleItem> SimpleItems)
        {
            XmlNode TokenNode;
            object ValueNode;

            if (SimpleItems == null || SimpleItems.Count == 0)
                return false;

            foreach (var si in SimpleItems)
            {
                TokenNode = TemplateNode.SelectSingleNode(string.Format("//w:t[text() = '{0}']", si.Key), TemplateNS);
                ValueNode = data.SelectSingle(si.XPath);
                if (TokenNode == null || ValueNode == null)
                {
                    if (TokenNode != null)
                        TokenNode.InnerXml = "{DataNotFound}";
                    else
                        _missingTokens.Add(si.Key);
                    continue;
                }

                SetValue(TokenNode, ValueNode, si);
            }
            return true;
        }

        private Hashtable BuildFormatData(XmlNodeList Metadata)
        {
            Hashtable Format;
            Format = new Hashtable();

            for (int i = 0; i < Metadata.Count; i++)
            {
                Format.Add(Metadata[i].Attributes["key"].Value, string.Format("{0}={1}", Metadata[i].Attributes[1].LocalName, Metadata[i].Attributes[1].Value));
            }
            return Format;
        }

        private void InserImage(XmlNode InsertNode, string relationshipID, Bitmap Image, string ImageName)
        {
            XmlNode workingNode = null;
            int widthInEmu, heightInEmu;

            workingNode = CreateChildElement("w", "drawing", WordObjects.WordProcessingML, InsertNode);
            CalculateImageEmus(Image, out widthInEmu, out heightInEmu);
            workingNode.InnerXml = CreateImageXml(relationshipID, ImageName, widthInEmu, heightInEmu);
        }

        string CreateImageXml(string relId, string imageName, int width, int height)
        {
            //Looking to add an image to WordprocessingML? Creating the xml is the easiest way. 
            string img = @"
                    <wp:inline xmlns:wp=""http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"">
                      <wp:extent cx=""" + width + @""" cy=""" + height + @""" />
                      <wp:docPr name=""" + imageName + @""" id=""1"" />
                      <a:graphic xmlns:a=""http://schemas.openxmlformats.org/drawingml/2006/main"">
                        <a:graphicData uri=""http://schemas.openxmlformats.org/drawingml/2006/picture"">
                          <pic:pic xmlns:pic=""http://schemas.openxmlformats.org/drawingml/2006/picture"">
                            <pic:nvPicPr>
                              <pic:cNvPr id=""0"" name=""" + imageName + @""" />
                              <pic:cNvPicPr />
                            </pic:nvPicPr>
                            <pic:blipFill>
                              <a:blip r:embed=""" + relId + @""" xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships"" />
                              <a:stretch>
                                <a:fillRect />
                              </a:stretch>
                            </pic:blipFill>
                            <pic:spPr>
                              <a:xfrm>
                                <a:off x=""0"" y=""0"" />
                                <a:ext cx=""" + width + @""" cy=""" + height + @""" />
                              </a:xfrm>
                              <a:prstGeom prst=""rect"" />
                            </pic:spPr>
                          </pic:pic>
                        </a:graphicData>
                      </a:graphic>
                    </wp:inline>";

            return img;
        }

        void CalculateImageEmus(Bitmap bitmap, out int widthInEmu, out int heightInEmu)
        {
            float verticalResolution = bitmap.VerticalResolution;
            float horizontalResolution = bitmap.HorizontalResolution;
            int width = bitmap.Size.Width;
            int height = bitmap.Size.Height;

            float widthInInches = (float)width / horizontalResolution;
            float heightInInches = (float)height / verticalResolution;

            widthInEmu = (int)(widthInInches * 914400);
            heightInEmu = (int)(heightInInches * 914400);
        }

        private static XmlNode CreateChildElement(string prefix, string nodeName, string xmlNamespace, XmlNode parentNode)
        {
            XmlNode child = parentNode.OwnerDocument.CreateElement(prefix, nodeName, xmlNamespace);
            parentNode.AppendChild(child);
            return child;
        }

        private XmlNamespaceManager GetNMFromXML(XmlDocument Doc)
        {
            XmlNamespaceManager XMLns = new XmlNamespaceManager(Doc.NameTable);
            IDictionary<string, string> nms;
            XPathNavigator nm = Doc.CreateNavigator();

            nm.MoveToFollowing(XPathNodeType.Element);

            nms = nm.GetNamespacesInScope(XmlNamespaceScope.All);

            foreach (KeyValuePair<string, string> e in nms)
            {
                XMLns.AddNamespace(e.Key, e.Value);
            }

            return XMLns;
        }

        //var sectPrs = wdDoc.MainDocumentPart.Document.Body.Elements<SectionProperties>();
        //foreach (var sectPr in sectPrs)
        //{
        //    var headers = sectPr.Elements<HeaderReference>();
        //    var footers = sectPr.Elements<FooterReference>();
        //    // Delete existing references to headers.
        //    //sectPr.RemoveAllChildren<HeaderReference>();

        //    // Create the new header reference node.
        //    //sectPr.PrependChild<HeaderReference>(new HeaderReference() { Id = rId });
        //} 
    }

    //var sectPrs = wdDoc.MainDocumentPart.Document.Body.Elements<SectionProperties>();
    //foreach (var sectPr in sectPrs)
    //{
    //    var headers = sectPr.Elements<HeaderReference>();
    //    var footers = sectPr.Elements<FooterReference>();
    //    // Delete existing references to headers.
    //    //sectPr.RemoveAllChildren<HeaderReference>();

    //    // Create the new header reference node.
    //    //sectPr.PrependChild<HeaderReference>(new HeaderReference() { Id = rId });
    //}   

}
